#include "Plume2D.h"

namespace liba {
namespace lib3d {
namespace node {

const Atom Plume2D::node_name("Plume2D");

Plume2D::Plume2D(const Atom & name, Node * parent) : Node(name, parent), _width(0.0f), _needUpdate(false), _transparency(0.0f), _color(1.0f, 1.0f, 1.0f)
{
	node_type = node_name;
	hard::Manager::add_dependent(this);
}

Plume2D::~Plume2D()
{
	hard::Manager::remove_dependent(this);
}

Plume2D * Plume2D::Clone()
{
	Plume2D * newPlume = new Plume2D(*this);
	hard::Manager::add_dependent(newPlume);
	return newPlume;
}

void Plume2D::on_add_hardware(hard::Manager::hardware_type * ha)
{
	hard_vector.push_back(SweetPair(ha, ha->create_vertex_buffer()));
}

void Plume2D::RenderToList(hard::Hardware * hardware, hard::RenderList * render_list)
{
	// validate points count
	if(_points.size() < 2)
		return;
	
	// getting object and check if it valid
	hard::VertexBuffer * vb = find_data(hardware);
	if(!vb->is_valid() || _needUpdate)
		rebuildPlume();
	
	// fill render model
	hard::RenderModel render_model;
	render_model.world                 = Matrix::translate(lib3d::Vector(0.0f, 0.0f, GetGlobalPosition().z));
	render_model.vb                    = vb;
	render_model.primitive_start       = 0;
	render_model.primitive_count       = (_points.size() - 1) * 2;
	render_model.ib                    = NULL;
	render_model.vertex_start          = 0;
	render_model.vertex_count          = render_model.primitive_count * 3;
	render_model.material.transparency = 0.001f;
	
	// add to render list
	render_list->models.push_back(render_model);
}

math::Vector2<float> Plume2D::transformPoint(const lib3d::Matrix & matrix, const math::Vector2<float> & point)
{
	lib3d::Vector point3D = matrix * lib3d::Vector(point.x, point.y, 0.0f);
	return math::Vector2<float>(point3D.x, point3D.y);
}
	
void Plume2D::rebuildPlume()
{
	// updated
	_needUpdate = true;
	
	// validate points count
	if(_points.size() < 2)
		return;
	
	// rotation matrix
	lib3d::Matrix rotation = lib3d::Matrix::rotate(lib3d::Vector(0.0f, 0.0f, -1.57f));
				   
	// array for coordinates
	SimpleArray<float> coordinates((_points.size() - 1) * 4 * 3 * 3);
	SimpleArray<float> colors((_points.size() - 1) * 4 * 3 * 4);
	float * coordinatesData = coordinates.data();
	float * colorsData      = colors.data();
	
	// direction of previous patch
	math::Vector2<float> startNode     = _points[0];
	math::Vector2<float> endNode       = _points[1];
	math::Vector2<float> prevDirection = transformPoint(rotation, math::Vector2<float>::normalize(endNode - startNode));
	
	// alpha value
	float alpha = 1.0f - _transparency;
	
	// fill VB data
	for(unsigned int i = 0; i < _points.size() - 1; i++)
	{
		// compute direction for current patch
		math::Vector2<float> startNode = _points[i + 0];
		math::Vector2<float> endNode   = _points[i + 1];
		math::Vector2<float> direction = transformPoint(rotation, math::Vector2<float>::normalize(endNode - startNode));

		// set vertices
		*coordinatesData++ = startNode.x - (_width / 2.0f) * prevDirection.x;
		*coordinatesData++ = startNode.y - (_width / 2.0f) * prevDirection.y;
		*coordinatesData++ = 0.0f;
		*colorsData++      = _color.r;
		*colorsData++      = _color.g;
		*colorsData++      = _color.b;
		*colorsData++      = alpha;
		
		*coordinatesData++ = endNode.x   - (_width / 2.0f) * direction.x;
		*coordinatesData++ = endNode.y   - (_width / 2.0f) * direction.y;
		*coordinatesData++ = 0.0f;
		*colorsData++      = _color.r;
		*colorsData++      = _color.g;
		*colorsData++      = _color.b;
		*colorsData++      = alpha;
		
		*coordinatesData++ = endNode.x   + (_width / 2.0f) * direction.x;
		*coordinatesData++ = endNode.y   + (_width / 2.0f) * direction.y;
		*coordinatesData++ = 0.0f;
		*colorsData++      = _color.r;
		*colorsData++      = _color.g;
		*colorsData++      = _color.b;
		*colorsData++      = alpha;

		*coordinatesData++ = endNode.x   + (_width / 2.0f) * direction.x;
		*coordinatesData++ = endNode.y   + (_width / 2.0f) * direction.y;
		*coordinatesData++ = 0.0f;
		*colorsData++      = _color.r;
		*colorsData++      = _color.g;
		*colorsData++      = _color.b;
		*colorsData++      = alpha;
		
		*coordinatesData++ = startNode.x + (_width / 2.0f) * prevDirection.x;
		*coordinatesData++ = startNode.y + (_width / 2.0f) * prevDirection.y;
		*coordinatesData++ = 0.0f;
		*colorsData++      = _color.r;
		*colorsData++      = _color.g;
		*colorsData++      = _color.b;
		*colorsData++      = alpha;
		
		*coordinatesData++ = startNode.x - (_width / 2.0f) * prevDirection.x;
		*coordinatesData++ = startNode.y - (_width / 2.0f) * prevDirection.y;
		*coordinatesData++ = 0.0f;
		*colorsData++      = _color.r;
		*colorsData++      = _color.g;
		*colorsData++      = _color.b;
		*colorsData++      = alpha;
		
		// save direction
		prevDirection      = direction;
	}
	
	// update vertex buffer
	SimpleArray<float> emptyFloat;
	HardVector::iterator it = hard_vector.begin();
	for(; it != hard_vector.end(); ++it)
	{
		if(!it->second->update(coordinates, emptyFloat, colors, emptyFloat, emptyFloat))
			THROW(Gluck(__FUNCTION__, "vb->update() failed"));
	}
}

bool Plume2D::on_attribute(xml::Provider * provider, const std::string & name, const std::string & value)
{
	if(name == "Width")
		return converter::convert(value, &_width);
	
	if(name == "Transparency")
		return converter::convert(value, &_transparency);
	
	if(name == "Color")
		return converter::convert(value, &_color);
	
	return Node::on_attribute(provider, name, value);
}
	
void Plume2D::add_point(const math::Vector2<float> & point)
{
	if(_points.size() > 0)
	{
		if((_points[_points.size() - 1] - point).length() < 10.0f)
			return;
	}
	
	_points.push_back(point);
	
	_needUpdate = true;
}

void Plume2D::clear()
{
	_points.clear();
	
	_needUpdate = true;
}
	
void Plume2D::set_width(float width)
{
	_width = width;
}
	
float Plume2D::get_width()
{
	return _width;
}
	
void Plume2D::set_color(const Color & color)
{
	_color = color;
}

Color Plume2D::get_color()
{
	return _color;
}
	
void Plume2D::set_transparency(float transparency)
{
	_transparency = transparency;
}
	
float Plume2D::get_transparency()
{
	return _transparency;
}
	
}
}
}